package com.renke.enml;

import org.codehaus.jparsec.OperatorTable;
import org.codehaus.jparsec.Parser;
import org.codehaus.jparsec.Parsers;
import org.codehaus.jparsec.Scanners;
import org.codehaus.jparsec.Terminals;
import org.codehaus.jparsec.functors.Binary;
import org.codehaus.jparsec.functors.Map;
import org.codehaus.jparsec.functors.Unary;

public class Calculator {

	enum BinaryOperator implements Binary<Expression> {
		PLUS {
			@Override
			public Expression map(Expression a, Expression b) {
				return new MathExpression(a, b, "+");
			}
		},
		MINUS {
			@Override
			public Expression map(Expression a, Expression b) {
				return new MathExpression(a, b, "-");
			}
		},
		MUL {
			@Override
			public Expression map(Expression a, Expression b) {
				return new MathExpression(a, b, "*");
			}
		},
		DIV {
			@Override
			public Expression map(Expression a, Expression b) {
				return new MathExpression(a, b, "/");
			}
		},
		MOD {
			@Override
			public Expression map(Expression a, Expression b) {
				return new MathExpression(a, b, "%");
			}
		}
	}

	enum UnaryOperator implements Unary<Expression> {
		NEG {
			@Override
			public Expression map(Expression n) {
				return ((MathExpression) n).negate();
			}
		}
	}

	static final Parser<Expression> NUMBER = Terminals.DecimalLiteral.PARSER.map(new Map<String, Expression>() {
		@Override
		public Expression map(String s) {
			return new NumberLiteral(s);
		}
	});

	private static final Terminals OPERATORS = Terminals.operators("+", "-", "*", "/", "%", "(", ")");

	static final Parser<Void> IGNORED = Parsers.or(Scanners.JAVA_LINE_COMMENT, Scanners.JAVA_BLOCK_COMMENT, Scanners.WHITESPACES).skipMany();

	static final Parser<?> TOKENIZER = Parsers.or((Parser<?>) Terminals.DecimalLiteral.TOKENIZER, OPERATORS.tokenizer());

	static Parser<?> term(String... names) {
		return OPERATORS.token(names);
	}

	static final Parser<BinaryOperator> WHITESPACE_MUL = term("+", "-", "*", "/", "%").not().retn(BinaryOperator.MUL);

	static <T> Parser<T> op(String name, T value) {
		return term(name).retn(value);
	}

	static Parser<Expression> calculator(Parser<Expression> atom) {
		Parser.Reference<Expression> ref = Parser.newReference();
		Parser<Expression> unit = ref.lazy().between(term("("), term(")")).or(atom);
		Parser<Expression> parser = new OperatorTable<Expression>().infixl(op("+", BinaryOperator.PLUS), 10).infixl(op("-", BinaryOperator.MINUS), 10)
				.infixl(op("*", BinaryOperator.MUL).or(WHITESPACE_MUL), 20).infixl(op("/", BinaryOperator.DIV), 20).infixl(op("%", BinaryOperator.MOD), 20)
				.prefix(op("-", UnaryOperator.NEG), 30).build(unit);
		ref.set(parser);
		return parser;
	}

	public static final Parser<Expression> CALCULATOR = calculator(NUMBER).from(TOKENIZER, IGNORED);

	public static void main(String[] args) {
		System.out.println(CALCULATOR.parse("9 * 2 + 11 % 10 "));
	}

	private static Parser<Expression> nn() {
		ENMLParser a = new ENMLParser();
		return Parsers.or(NUMBER, a.variable(), a.constant());
	}
}